package InterDomain;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Iterator;

import Objects.Logger;
import Objects.Parser;
import Objects.Path;
import Objects.Router;

public class InterRouter extends Router {
	public void start() {
		UpdateSender u = new UpdateSender();
		Thread t = new Thread(u);
		t.start();
	}

	private static void sendUpdateMessage() {
		ArrayList<String> iNeighborIPs = new ArrayList<String>();
		ArrayList<String> gNeighborIPs = new ArrayList<String>();
		iNeighborIPs = myNeighbors.getIntraNeighborIPs();
		gNeighborIPs = myNeighbors.getInterNeighborIPs();

		// send the i router update messages to all my i router neighbors
		for (int i = 0; i < iNeighborIPs.size(); i++) {
			ArrayList<String> datas = new ArrayList<String>();
			if (iNeighborIPs.get(i) != null) {
				Iterator<String> it = myRoutingTable.getDestination().keySet().iterator();
				while (it.hasNext()) {
					String destination = it.next();
					char desIG = myRoutingTable.getDestination().get(destination).getIG();
					// If my neighbor i am trying to send this update message
					// is:
					// 1. an i router
					// 2. not the destination router of this update message
					if (desIG == 'i'
							&& !destination.equals(myNeighbors.getMyNeighbors().get(i).getRouterID())) {

						String updateMessage = "";
						updateMessage += myConfig.getRouterID() + ", " + myConfig.getIG() + ", "
								+ destination + ", " + desIG + ", "
								+ myRoutingTable.getDestination().get(destination).getHopCount() + ", "
								+ myRoutingTable.getDestination().get(destination).getBestPath().printPath()
								+ ", Update";
						datas.add(updateMessage);
						// also send my backup paths for any particular
						// destination
						for (int j = 0; j < myRoutingTable.getDestination().get(destination).getBackupPaths()
								.size(); j++) {
							String backupUpdateMessage = "";
							Path currentBackupPath = myRoutingTable.getDestination().get(destination)
									.getBackupPaths().get(j);
							backupUpdateMessage += myConfig.getRouterID() + ", " + myConfig.getIG() + ", "
									+ destination + ", " + desIG + ", " + currentBackupPath.getHopCount()
									+ ", " + currentBackupPath.printPath() + ", Update";
							datas.add(backupUpdateMessage);
						}
					}
				}
			}
			ArraySender updateSender = new ArraySender(iNeighborIPs.get(i), 9876, datas);
			Thread t = new Thread(updateSender);
			t.start();
		}

		// send the g router update message to all my g router neighbors
		// and send my i router lists for all my known g routers to my g
		// neighbors
		for (int i = 0; i < gNeighborIPs.size(); i++) {
			ArrayList<String> datas = new ArrayList<String>();
			if (gNeighborIPs.get(i) != null) {
				Iterator<String> it = myRoutingTable.getDestination().keySet().iterator();
				while (it.hasNext()) {
					String destination = it.next();
					char desIG = myRoutingTable.getDestination().get(destination).getIG();

					if (desIG == 'g'
							&& !destination.equals(myNeighbors.getMyNeighbors().get(i).getRouterID())) {
						// sending the update messages
						String updateMessage = "";
						updateMessage += myConfig.getRouterID() + ", " + myConfig.getIG() + ", "
								+ destination + ", " + desIG + ", "
								+ myRoutingTable.getDestination().get(destination).getHopCount() + ", "
								+ myRoutingTable.getDestination().get(destination).getBestPath().printPath()
								+ ", Update";
						datas.add(updateMessage);
						for (int j = 0; j < myRoutingTable.getDestination().get(destination).getBackupPaths()
								.size(); j++) {
							String backupUpdateMessage = "";
							Path currentBackupPath = myRoutingTable.getDestination().get(destination)
									.getBackupPaths().get(j);
							backupUpdateMessage += myConfig.getRouterID() + ", " + myConfig.getIG() + ", "
									+ destination + ", " + desIG + ", " + currentBackupPath.getHopCount()
									+ ", " + currentBackupPath.printPath() + ", Update";
							datas.add(backupUpdateMessage);
						}

						// sending the intra messages
						String intraMessage = "";
						intraMessage += myConfig.getRouterID() + ", " + myConfig.getIG() + ", "
								+ myRoutingTable.printMyIntraRouters() + ", Intra";
						datas.add(intraMessage);

						intraMessage = "";
						intraMessage += destination + ", " + desIG + ", "
								+ myRoutingTable.getDestination().get(destination).printIntraRouters()
								+ ", Intra";
						datas.add(intraMessage);
					}
				}
			}
			ArraySender updateSender = new ArraySender(gNeighborIPs.get(i), 9876, datas);
			Thread t = new Thread(updateSender);
			t.start();
		}
	}

	/**
	 * Takes in an array list of data to send to the same destination This is
	 * used to send update messages
	 * 
	 * @author jxa074000
	 * 
	 */
	private static class ArraySender extends Thread {
		String ip = null;
		int port = -1;
		ArrayList<String> datas = null;

		private ArraySender(String ip, int port, ArrayList<String> datas) {
			this.ip = ip;
			this.port = port;
			this.datas = datas;
		}

		public void run() {
			for (int i = 0; i < datas.size(); i++) {
				try {
					String[] splitString = datas.get(i).split(",");
					String messageType = splitString[splitString.length - 1].trim();
					byte[] receiveData = new byte[10];
					DatagramSocket clientSocket = new DatagramSocket();
					InetAddress ipAddress = InetAddress.getByName(ip);
					Logger.log(
							Logger.DEBUG,
							"Trying to send packet: " + datas.get(i) + " to: "
									+ Parser.getRouterID(ipAddress.getHostAddress()) + " (" + ip + ")");
					DatagramPacket sendPacket = new DatagramPacket(datas.get(i).getBytes(), datas.get(i)
							.getBytes().length, ipAddress, port);
					clientSocket.send(sendPacket);

					DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
					clientSocket.setSoTimeout(10000);

					clientSocket.receive(receivePacket);
					String ack = new String(receivePacket.getData());

					if (ack.trim().equals("Ack")) {
						Logger.log(Logger.DEBUG, "Ack recieved from " + Parser.getRouterID(ip) + " "
								+ messageType + " Message");
					} else {
						Logger.log(Logger.DEBUG, "Supposed to be Ack from" + Parser.getRouterID(ip)
								+ ", but Recieved: " + ack);
					}
					clientSocket.disconnect();
					clientSocket.close();
				} catch (SocketTimeoutException e) {
					Logger.log(Logger.DEBUG, "Ack from " + Parser.getRouterID(ip)
							+ " timeout occurred: Ack packet assume lost");
					Logger.log(Logger.DEBUG, e.getStackTrace());
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	private class UpdateSender extends Thread {
		public void run() {
			while (true) {
				InterRouter.sendUpdateMessage();
				try {
					Thread.sleep(10000);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

}
